﻿using System;
using System.ComponentModel;

namespace MathService
{
    /// <summary>Класс объектов-свойств, определяемых методами установки и чтения значения</summary>
    public class LambdaProperty<T> : INotifyPropertyChanged, IEquatable<LambdaProperty<T>>
    {
        private static readonly PropertyChangedEventArgs sf_Args = new PropertyChangedEventArgs("Value");

        private event PropertyChangedEventHandler e_PropertyChanged;
        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
        {
            [System.Diagnostics.DebuggerStepThrough]
            add
            {
                if(value != null) lock(e_PropertyChanged)
                        if(e_PropertyChanged == null)
                            e_PropertyChanged = value;
                        else
                            e_PropertyChanged += value;
            }
            [System.Diagnostics.DebuggerStepThrough]
            remove
            {
                if(value != null) lock(e_PropertyChanged)
                        if(e_PropertyChanged != null)
                            e_PropertyChanged -= value;
            }
        }


        /// <summary>Метод получения значения свойства</summary>
        private Func<T> f_GetMethod;

        /// <summary>Метод установки значения свойства</summary>
        private Action<T> f_SetMethod;

        /// <summary>Метод получения значения свойства</summary>
        public Func<T> GetMethod
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return f_GetMethod; }
            [System.Diagnostics.DebuggerStepThrough]
            set { f_GetMethod = value; }
        }

        /// <summary>Метод установки значения свойства</summary>
        public Action<T> SetMethod
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return f_SetMethod; }
            [System.Diagnostics.DebuggerStepThrough]
            set { f_SetMethod = value; }
        }

        /// <summary>Признак возможности чтения значения свойства (если задан метод чтения)</summary>
        public bool CanRead { [System.Diagnostics.DebuggerStepThrough]get { return f_GetMethod != null; } }

        /// <summary>Признак возможности устанавливать значение свойства (если задан метод записи)</summary>
        public bool CanWrite { [System.Diagnostics.DebuggerStepThrough]  get { return f_SetMethod != null; } }

        /// <summary>Значение свойства</summary>
        public T Value
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return f_GetMethod(); }
            [System.Diagnostics.DebuggerStepThrough]
            set
            {
                f_SetMethod(value);
                if(e_PropertyChanged != null) e_PropertyChanged(this, sf_Args);
            }
        }

        /// <summary>Новое лямда свойство</summary>
        /// <param name="GetMethod">Метод чтения значения</param>
        /// <param name="SetMethod">Метод записи значения</param>
        [System.Diagnostics.DebuggerStepThrough]
        public LambdaProperty(Func<T> GetMethod, Action<T> SetMethod)
        {
            this.GetMethod = GetMethod;
            this.SetMethod = SetMethod;
        }

        public override string ToString()
        {
            return string.Format("lProperty({0}.{1}){2}",
                                 CanRead ? "R" : "",
                                 CanWrite ? "W" : "",
                                 CanRead ? string.Format(":Value = {0}", Value) : "");
        }

        public override bool Equals(object obj)
        {
            if(ReferenceEquals(null, obj)) return false;
            if(ReferenceEquals(this, obj)) return true;
            if(obj.GetType() != typeof(LambdaProperty<T>)) return false;
            return Equals((LambdaProperty<T>)obj);
        }

        /// <summary>Указывает, равен ли текущий объект другому объекту того же типа.</summary>
        /// <returns>true, если текущий объект равен параметру <paramref name="other"/>, в противном случае — false.</returns>
        /// <param name="other">Объект, который требуется сравнить с данным объектом.</param>
        public bool Equals(LambdaProperty<T> other)
        {
            if(ReferenceEquals(null, other)) return false;
            if(ReferenceEquals(this, other)) return true;
            return Equals(other.f_GetMethod, f_GetMethod) && Equals(other.f_SetMethod, f_SetMethod);
        }

        /// <summary>Играет роль хэш-функции для определенного типа. </summary>
        /// <returns>Хэш-код для текущего объекта <see cref="T:System.Object"/>.</returns>
        /// <filterpriority>2</filterpriority>
        public override int GetHashCode()
        {
            unchecked
            {
                return ((f_GetMethod != null ? f_GetMethod.GetHashCode() : 0) * 397) ^ (f_SetMethod != null ? f_SetMethod.GetHashCode() : 0);
            }
        }
    }
}
